/*
 * @CopyRight:
 * FISCO-BCOS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FISCO-BCOS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FISCO-BCOS.  If not, see <http://www.gnu.org/licenses/>
 * (c) 2016-2018 fisco-dev contributors.
 */

/**
 * @brief: unit test for libconsensus/pbft/PBFTConsensus.*
 * @file: PBFTConsensus.cpp
 * @author: yujiechen
 * @date: 2018-10-09
 */
#include "PBFTEngine.h"
#include "Common.h"
#include <test/tools/libutils/TestOutputHelper.h>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
namespace dev
{
namespace test
{
std::shared_ptr<FakeSession> FakeSessionFunc(Public node_id)
{
    std::shared_ptr<FakeSession> session = std::make_shared<FakeSession>(node_id);
    return session;
}

void testCheckReq(FakeConsensus<FakePBFTEngine>& fake_pbft, PrepareReq::Ptr prepareReq,
    SignReq& signReq, bool succ)
{
    if (!succ)
    {
        fake_pbft.consensus()->reqCache()->clearAll();
        /// test is the future block
        FakeValidNodeNum(fake_pbft, 4);
        SignReq copiedReq = signReq;
        copiedReq.height = fake_pbft.consensus()->consensusBlockNumber() + 1;
        FakeValidNodeNum(fake_pbft, 4);
        BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(copiedReq) == CheckResult::FUTURE);
        BOOST_CHECK(fake_pbft.consensus()->reqCache()->isExistSign(copiedReq) == true);
        BOOST_CHECK(
            fake_pbft.consensus()->reqCache()->getSigCacheSize(copiedReq.block_hash, 1) == 0);
        BOOST_CHECK(fake_pbft.consensus()->reqCache()->isExistSign(copiedReq) == false);
        /// modify the signature
        copiedReq.sig =
            dev::crypto::Sign(fake_pbft.m_keyPair[copiedReq.idx], copiedReq.block_hash)->asBytes();
        copiedReq.sig2 =
            dev::crypto::Sign(fake_pbft.m_keyPair[copiedReq.idx], copiedReq.fieldsWithoutBlock())
                ->asBytes();
        BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(copiedReq) == CheckResult::FUTURE);
        BOOST_CHECK(fake_pbft.consensus()->reqCache()->isExistSign(copiedReq) == true);

        fake_pbft.consensus()->reqCache()->clearAll();
        PrepareReq::Ptr copiedPrepareReq = std::make_shared<PrepareReq>(*prepareReq);
        fake_pbft.consensus()->reqCache()->addPrepareReq(copiedPrepareReq);
        /// test signReq is generated by the node-self
        IDXTYPE org_idx = signReq.idx;
        signReq.idx = fake_pbft.consensus()->nodeIdx();
        BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(signReq) == CheckResult::INVALID);
        signReq.idx = org_idx;
        /// test invalid view
        VIEWTYPE org_view = signReq.view;
        signReq.view += 1;
        BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(signReq) == CheckResult::INVALID);
        signReq.view = org_view;
        /// test invalid sign
        auto sig = signReq.sig;
        signReq.sig.clear();
        BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(signReq) == CheckResult::INVALID);
        signReq.sig = sig;
    }
}

void testReHandleCommitPrepareCache(FakeConsensus<FakePBFTEngine>& fake_pbft, PrepareReq const& req)
{
    /// check callback broadcastMsg
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                        fake_pbft.m_sealerList[i], PrepareReqPacket, req.uniqueKey()) == false);
    }
}

void fakeValidViewchange(FakeConsensus<FakePBFTEngine>& fake_pbft, ViewChangeReq& req,
    IDXTYPE const& idx, bool forFastViewChange, bool setToView)
{
    FakeBlockChain* p_blockChain =
        dynamic_cast<FakeBlockChain*>(fake_pbft.consensus()->blockChain().get());
    BlockHeader highest = p_blockChain->getBlockByNumber(p_blockChain->number())->header();
    /// fake_pbft.consensus()->resetConfig();
    fake_pbft.consensus()->setHighest(highest);
    fake_pbft.consensus()->setView(2);
    req.idx = idx;
    fake_pbft.consensus()->setNodeIdx((req.idx + 1) % fake_pbft.m_sealerList.size());
    req.view = 3;
    if (setToView)
    {
        if (!forFastViewChange)
        {
            fake_pbft.consensus()->setToView(req.view);
        }
        else
        {
            fake_pbft.consensus()->setToView(req.view - 2);
        }
    }

    req.block_hash = highest.hash();
    req.height = highest.number();
    fake_pbft.consensus()->setConsensusBlockNumber(req.height + 1);
    auto keyPair = fake_pbft.m_keyPair[req.idx];
    req.sig = dev::crypto::Sign(keyPair, req.block_hash)->asBytes();
    req.sig2 = dev::crypto::Sign(keyPair, req.fieldsWithoutBlock())->asBytes();
}

void testIsExistCommit(FakeConsensus<FakePBFTEngine>& fake_pbft, PrepareReq::Ptr prepareReq,
    CommitReq::Ptr commitReq, bool succ)
{
    if (!succ)
    {
        fake_pbft.consensus()->reqCache()->addCommitReq(commitReq);
        BOOST_CHECK(fake_pbft.consensus()->isValidCommitReq(*commitReq) == CheckResult::INVALID);
        fake_pbft.consensus()->reqCache()->clearAll();
        fake_pbft.consensus()->reqCache()->addPrepareReq(prepareReq);
    }
}

void testIsExistSign(FakeConsensus<FakePBFTEngine>& fake_pbft, PrepareReq::Ptr prepareReq,
    SignReq::Ptr signReq, bool succ)
{
    if (!succ)
    {
        fake_pbft.consensus()->reqCache()->addSignReq(signReq);
        BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(*signReq) == CheckResult::INVALID);
        PrepareReq::Ptr copiedPrepareReq = std::make_shared<PrepareReq>(*prepareReq);
        fake_pbft.consensus()->reqCache()->addPrepareReq(copiedPrepareReq);
        fake_pbft.consensus()->reqCache()->clearAll();
    }
}

/// test isValidSignReq
void TestIsValidSignReq(FakeConsensus<FakePBFTEngine>& fake_pbft, PBFTMsgPacket& packet,
    SignReq::Ptr signReq, PrepareReq::Ptr prepareReq, KeyPair const& peer_keyPair, bool succ)
{
    FakeValidSignorCommitReq(fake_pbft, packet, *signReq, prepareReq, peer_keyPair);
    BOOST_CHECK(fake_pbft.consensus()->isValidSignReq(*signReq) == CheckResult::VALID);
    testIsExistSign(fake_pbft, prepareReq, signReq, succ);
    testCheckReq(fake_pbft, prepareReq, *signReq, succ);
}

/// test isValidViewChangeReq
void TestIsValidViewChange(FakeConsensus<FakePBFTEngine>& fake_pbft, ViewChangeReq::Ptr req)
{
    IDXTYPE nodeIdx = 0;
    IDXTYPE nodeIdxSource = 2;
    fakeValidViewchange(fake_pbft, *req);

    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == true);

    /// case 1: own viewchange request
    IDXTYPE orgIdx = fake_pbft.consensus()->nodeIdx();
    fake_pbft.consensus()->setNodeIdx(nodeIdx);
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdx) == false);
    fake_pbft.consensus()->setNodeIdx(orgIdx);

    /// case 2: existed viewchange
    fake_pbft.consensus()->reqCache()->addViewChangeReq(req);
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == false);
    fake_pbft.consensus()->reqCache()->clearAll();
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == true);

    /// case 6: catchupView(send viewchange message to the source)
    fake_pbft.consensus()->setToView(req->view + 2);
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, req->idx) == true);
    compareAsyncSendTime(fake_pbft, fake_pbft.m_sealerList[req->idx], 1);
    fake_pbft.consensus()->setToView(req->view);

    /// case 3: check height and view
    uint64_t orgHeight = req->height;
    req->height = req->height - 1;
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == false);
    req->height = orgHeight;
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == true);

    /// case 4: check block hash
    dev::h256 orgHash = req->block_hash;
    req->block_hash = crypto::Hash("fake");
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == false);
    req->block_hash = orgHash;
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == true);

    /// case 5: check sign
    req->view += 1;
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == false);
    req->view -= 1;
    BOOST_CHECK(fake_pbft.consensus()->isValidViewChangeReq(*req, nodeIdxSource) == true);
}

void TestIsValidCommitReq(FakeConsensus<FakePBFTEngine>& fake_pbft, PBFTMsgPacket& packet,
    CommitReq::Ptr commitReq, PrepareReq::Ptr prepareReq, KeyPair const& peer_keyPair, bool succ)
{
    FakeValidSignorCommitReq(fake_pbft, packet, *commitReq, prepareReq, peer_keyPair);
    BOOST_CHECK(fake_pbft.consensus()->isValidCommitReq(*commitReq) == CheckResult::VALID);
    testIsExistCommit(fake_pbft, prepareReq, commitReq, succ);
}

void checkPrepareReqEqual(PrepareReq::Ptr _first, PrepareReq::Ptr _second)
{
    BOOST_CHECK(_first->block_hash == _second->block_hash);
    BOOST_CHECK(_first->height == _second->height);
    BOOST_CHECK(_first->idx == _second->idx);
    BOOST_CHECK(_first->sig == _second->sig);
    BOOST_CHECK(_first->timestamp == _second->timestamp);
    BOOST_CHECK(_first->view == _second->view);
    BOOST_CHECK(_first->sig2 == _second->sig2);
}

BOOST_FIXTURE_TEST_SUITE(pbftConsensusTest, TestOutputHelperFixture)

/// test constructor (invalid ProtocolID will cause exception)
/// test initPBFTEnv (normal case)
BOOST_AUTO_TEST_CASE(testInitPBFTEnvNormalCase)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    BOOST_CHECK(fake_pbft.consensus()->keyPair().pub() != h512());
    BOOST_CHECK(fake_pbft.consensus()->broadCastCache());
    BOOST_CHECK(fake_pbft.consensus()->reqCache());
    /// init pbft env
    fake_pbft.consensus()->initPBFTEnv(
        fake_pbft.consensus()->timeManager().m_emptyBlockGenTime * 3);
    /// check level db has already been openend
    BOOST_CHECK(fake_pbft.consensus()->backupDB());
    BOOST_CHECK(fake_pbft.consensus()->consensusBlockNumber() == 0);
    BOOST_CHECK(fake_pbft.consensus()->toView() == 0);
    BOOST_CHECK(fake_pbft.consensus()->view() == 0);
    /// check resetConfig
    BOOST_CHECK(fake_pbft.consensus()->accountType() != NodeAccountType::SealerAccount);
    fake_pbft.m_sealerList.push_back(fake_pbft.consensus()->keyPair().pub());
    fake_pbft.consensus()->appendSealer(fake_pbft.consensus()->keyPair().pub());
    fake_pbft.consensus()->resetConfig();
    BOOST_CHECK(fake_pbft.consensus()->accountType() == NodeAccountType::SealerAccount);
    BOOST_CHECK(fake_pbft.consensus()->nodeIdx() == fake_pbft.m_sealerList.size() - 1);
    BOOST_CHECK(fake_pbft.consensus()->sealerList().size() == fake_pbft.m_sealerList.size());
    BOOST_CHECK(fake_pbft.consensus()->nodeNum() == fake_pbft.m_sealerList.size());
    BOOST_CHECK(fake_pbft.consensus()->fValue() == (fake_pbft.consensus()->nodeNum() - 1) / 3);

    /// check reloadMsg: empty committedPrepareCache
    checkPBFTMsg(fake_pbft.consensus()->reqCache()->committedPrepareCache());
    /// check m_timeManager
    BOOST_CHECK(fake_pbft.consensus()->timeManager().m_viewTimeout ==
                fake_pbft.consensus()->timeManager().m_emptyBlockGenTime * 3);
    BOOST_CHECK(fake_pbft.consensus()->timeManager().m_changeCycle == 0);
    BOOST_CHECK(fake_pbft.consensus()->timeManager().m_lastGarbageCollection <=
                std::chrono::steady_clock::now());
}

/// test onRecvPBFTMessage
BOOST_AUTO_TEST_CASE(testOnRecvPBFTMessage)
{
    /// fake FakePBFTEngine
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    KeyPair key_pair;
    /// fake prepare_req
    PrepareReq prepare_req = FakePrepareReq(key_pair);
    NodeIPEndpoint endpoint;
    /// fake session
    std::shared_ptr<FakeSession> session = FakeSessionFunc(key_pair.pub());
    ///------ test invalid case(recv message from own-node)
    /// check onreceive prepare request
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session, prepare_req, PrepareReqPacket, false);
    /// check onreceive sign request
    SignReq sign_req(prepare_req, key_pair, prepare_req.idx + 100);
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session, sign_req, SignReqPacket, false);
    /// check onReceive commit request
    CommitReq commit_req(prepare_req, key_pair, prepare_req.idx + 10);
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session, commit_req, CommitReqPacket, false);
    /// test viewchange case
    ViewChangeReq viewChange_req(
        key_pair, prepare_req.height, prepare_req.view, prepare_req.idx, prepare_req.block_hash);
    CheckOnRecvPBFTMessage(
        fake_pbft.consensus(), session, viewChange_req, ViewChangeReqPacket, false);

    KeyPair key_pair2 = KeyPair::create();
    std::shared_ptr<FakeSession> session2 = FakeSessionFunc(fake_pbft.m_sealerList[0]);
    /// test invalid case: this node is not sealer
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session2, prepare_req, PrepareReqPacket, false);
    ///----- test valid case
    /// test recv packet from other nodes
    FakePBFTSealer(fake_pbft);  // set this node to be sealer
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session2, prepare_req, PrepareReqPacket, true);
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session2, sign_req, SignReqPacket, true);
    CheckOnRecvPBFTMessage(fake_pbft.consensus(), session2, commit_req, CommitReqPacket, true);
    CheckOnRecvPBFTMessage(
        fake_pbft.consensus(), session2, viewChange_req, ViewChangeReqPacket, true);
}
/// test broadcastMsg
BOOST_AUTO_TEST_CASE(testBroadcastMsg)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    KeyPair peer_keyPair = KeyPair::create();
    /// append session info1
    appendSessionInfo(fake_pbft, peer_keyPair.pub());
    /// append session info2
    KeyPair peer2_keyPair = KeyPair::create();
    appendSessionInfo(fake_pbft, peer2_keyPair.pub());
    KeyPair key_pair;
    /// fake prepare_req
    PrepareReq prepare_req = FakePrepareReq(key_pair);
    bytes data;
    prepare_req.encode(data);
    /// case1: all peer is not the sealer, stop broadcasting
    fake_pbft.consensus()->broadcastMsgWrapper(PrepareReqPacket, prepare_req, ref(data));
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == false);
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer2_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == false);
    compareAsyncSendTime(fake_pbft, peer_keyPair.pub(), 0);
    compareAsyncSendTime(fake_pbft, peer2_keyPair.pub(), 0);

    /// case2: only one peer is the sealer, broadcast to one node
    fake_pbft.m_sealerList.push_back(peer_keyPair.pub());
    fake_pbft.consensus()->appendSealer(peer_keyPair.pub());
    FakePBFTSealer(fake_pbft);
    fake_pbft.consensus()->broadcastMsgWrapper(PrepareReqPacket, prepare_req, ref(data));

    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == true);
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer2_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == false);
    compareAsyncSendTime(fake_pbft, peer_keyPair.pub(), 1);
    compareAsyncSendTime(fake_pbft, peer2_keyPair.pub(), 0);

    /// case3: two nodes are all sealers , but one peer is in the cache, broadcast to one node
    fake_pbft.m_sealerList.push_back(peer2_keyPair.pub());
    fake_pbft.consensus()->appendSealer(peer2_keyPair.pub());
    FakePBFTSealer(fake_pbft);
    fake_pbft.consensus()->broadcastMsgWrapper(PrepareReqPacket, prepare_req, ref(data));

    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == true);
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer2_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == true);
    compareAsyncSendTime(fake_pbft, peer_keyPair.pub(), 1);
    compareAsyncSendTime(fake_pbft, peer2_keyPair.pub(), 1);

    /// case3: the sealer-peer in the filter
    KeyPair peer3_keyPair = KeyPair::create();
    appendSessionInfo(fake_pbft, peer3_keyPair.pub());
    fake_pbft.m_sealerList.push_back(peer3_keyPair.pub());
    fake_pbft.consensus()->appendSealer(peer3_keyPair.pub());
    FakePBFTSealer(fake_pbft);
    /// fake the filter with node id of peer3
    std::unordered_set<h512> filter;
    filter.insert(peer3_keyPair.pub());
    fake_pbft.consensus()->broadcastMsgWrapper(PrepareReqPacket, prepare_req, ref(data), filter);

    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer3_keyPair.pub(), PrepareReqPacket, prepare_req.uniqueKey()) == true);
    compareAsyncSendTime(fake_pbft, peer3_keyPair.pub(), 0);
}

/// test broadcastSignReq and broadcastCommitReq
BOOST_AUTO_TEST_CASE(testBroadcastSignAndCommitReq)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    /// check broadcastSignReq
    SignReq sign_req;
    checkBroadcastSpecifiedMsg(fake_pbft, sign_req, SignReqPacket);
    /// check broadcastCommitReq
    CommitReq commit_req;
    checkBroadcastSpecifiedMsg(fake_pbft, commit_req, CommitReqPacket);
}

/// test broadcastViewChangeReq
BOOST_AUTO_TEST_CASE(testBroadcastViewChangeReq)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);

    FakeBlockChain* p_blockChain =
        dynamic_cast<FakeBlockChain*>(fake_pbft.consensus()->blockChain().get());
    assert(p_blockChain);
    BlockHeader highest = p_blockChain->getBlockByNumber(p_blockChain->number())->header();
    fake_pbft.consensus()->setHighest(highest);

    ViewChangeReq viewChange_req(fake_pbft.consensus()->keyPair(), highest.number(),
        fake_pbft.consensus()->toView(), 0, highest.hash());
    std::string key = viewChange_req.uniqueKey();

    KeyPair peer_keyPair = KeyPair::create();
    /// append session info
    appendSessionInfo(fake_pbft, peer_keyPair.pub());

    /// case1: the peer node is not sealer
    fake_pbft.consensus()->broadcastViewChangeReq();
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer_keyPair.pub(), ViewChangeReqPacket, key) == false);
    compareAsyncSendTime(fake_pbft, peer_keyPair.pub(), 0);

    /// case2: the the peer node is a sealer
    fake_pbft.m_sealerList.push_back(peer_keyPair.pub());
    fake_pbft.consensus()->appendSealer(peer_keyPair.pub());
    FakePBFTSealer(fake_pbft);
    fake_pbft.consensus()->broadcastViewChangeReq();
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer_keyPair.pub(), ViewChangeReqPacket, key) == false);
    compareAsyncSendTime(fake_pbft, peer_keyPair.pub(), 1);
}

/// test timeout
BOOST_AUTO_TEST_CASE(testTimeout)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    fake_pbft.consensus()->initPBFTEnv(
        3 * fake_pbft.consensus()->timeManager().m_emptyBlockGenTime);

    VIEWTYPE orgToView = fake_pbft.consensus()->toView();
    TimeManager& timeManager = fake_pbft.consensus()->mutableTimeManager();
    unsigned orgChangeCycle = timeManager.m_changeCycle;

    ///< expect to no timeout
    timeManager.m_lastConsensusTime = timeManager.m_lastSignTime = utcSteadyTime();
    fake_pbft.consensus()->checkTimeout();
    BOOST_CHECK(fake_pbft.consensus()->toView() == orgToView);

    ///< expect to timeout, first timeout interval is 3000 because m_changeCycle is 0
    timeManager.m_lastAddRawPrepareTime = utcSteadyTime() - 5800;
    timeManager.m_lastConsensusTime = timeManager.m_lastSignTime = utcSteadyTime() - 5000;
    fake_pbft.consensus()->checkTimeout();
    BOOST_CHECK(fake_pbft.consensus()->toView() == orgToView + 1);
    BOOST_CHECK(timeManager.m_changeCycle == orgChangeCycle + 1);

    ///< expect to no timeout
    fake_pbft.consensus()->checkTimeout();
    BOOST_CHECK(fake_pbft.consensus()->toView() == orgToView + 1);
    BOOST_CHECK(timeManager.m_changeCycle == orgChangeCycle + 1);
}

/// test checkAndChangeView
BOOST_AUTO_TEST_CASE(testCheckAndChangeView)
{
    // 7 nodes
    FakeConsensus<FakePBFTEngine> fake_pbft(7, ProtocolID::PBFT);
    fake_pbft.consensus()->initPBFTEnv(
        3 * fake_pbft.consensus()->timeManager().m_emptyBlockGenTime);
    fake_pbft.consensus()->resetConfig();

    ///< timeout situation
    VIEWTYPE orgToView = fake_pbft.consensus()->toView();
    TimeManager& timeManager = fake_pbft.consensus()->mutableTimeManager();
    unsigned oriChangeCycle = timeManager.m_changeCycle;

    timeManager.m_lastAddRawPrepareTime = utcSteadyTime() - 5800;
    timeManager.m_lastConsensusTime = timeManager.m_lastSignTime = utcSteadyTime() - 5000;
    fake_pbft.consensus()->checkTimeout();
    BOOST_CHECK(fake_pbft.consensus()->toView() == orgToView + 1);
    BOOST_CHECK(timeManager.m_changeCycle == oriChangeCycle + 1);

    FakeBlockChain* p_blockChain =
        dynamic_cast<FakeBlockChain*>(fake_pbft.consensus()->blockChain().get());
    assert(p_blockChain);
    BlockHeader highest = p_blockChain->getBlockByNumber(p_blockChain->number())->header();
    fake_pbft.consensus()->setHighest(highest);

    ///< receiving another 4 viewchange mesages will trigger view change in consensus
    VIEWTYPE oriview = fake_pbft.consensus()->view();
    for (size_t i = 1; i < fake_pbft.m_sealerList.size(); i++)
    {
        ViewChangeReq::Ptr viewChange_req =
            std::make_shared<ViewChangeReq>(KeyPair(fake_pbft.m_secrets[i]), highest.number(),
                fake_pbft.consensus()->toView(), i, highest.hash());
        fake_pbft.consensus()->reqCache()->addViewChangeReq(viewChange_req);
        IDXTYPE size =
            fake_pbft.consensus()->reqCache()->getViewChangeSize(fake_pbft.consensus()->toView());
        fake_pbft.consensus()->checkAndChangeView();
        if (3 >= size)
        {
            BOOST_CHECK(fake_pbft.consensus()->view() == oriview);
        }
        else if (4 == size)
        {
            ///< view change triggered, view += 1
            BOOST_CHECK(fake_pbft.consensus()->view() == fake_pbft.consensus()->toView());
            break;
        }
    }
}

/// test checkAndSave && reportBlock
BOOST_AUTO_TEST_CASE(testCheckAndSave)
{
    /// fake collected commitReq and signReq
    BlockHeader highest;
    size_t invalid_height = 2;
    size_t invalid_hash = 0;
    size_t valid = 4;
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    fake_pbft.consensus()->initPBFTEnv(
        3 * fake_pbft.consensus()->timeManager().m_emptyBlockGenTime);
    PrepareReq::Ptr prepare_req = std::make_shared<PrepareReq>();
    FakeValidNodeNum(fake_pbft, valid);
    FakeSignAndCommitCache(fake_pbft, prepare_req, highest, invalid_height, invalid_hash, valid, 2);
    /// exception case: invalid view
    fake_pbft.consensus()->setView(prepare_req->view + 1);
    CheckBlockChain(fake_pbft, false);
    /// exception case2: invalid height
    fake_pbft.consensus()->setView(prepare_req->view);
    fake_pbft.consensus()->mutableHighest().setNumber(prepare_req->height);
    CheckBlockChain(fake_pbft, false);
    /// normal case: test checkAndSave(import success)
    fake_pbft.consensus()->mutableHighest().setNumber(prepare_req->height - 1);
    CheckBlockChain(fake_pbft, true);
}
/// test checkAndCommit
BOOST_AUTO_TEST_CASE(testCheckAndCommit)
{
    /// fake collected commitReq and signReq
    BlockHeader highest;
    size_t invalid_height = 2;
    size_t invalid_hash = 0;
    size_t valid = 4;
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    fake_pbft.consensus()->initPBFTEnv(
        3 * fake_pbft.consensus()->timeManager().m_emptyBlockGenTime);
    int64_t block_number = obtainBlockNumber(fake_pbft);
    PrepareReq::Ptr prepare_req = std::make_shared<PrepareReq>();

    KeyPair peer_keyPair = KeyPair::create();
    fake_pbft.m_sealerList.push_back(peer_keyPair.pub());
    fake_pbft.consensus()->appendSealer(peer_keyPair.pub());

    FakeValidNodeNum(fake_pbft, valid);
    FakeSignAndCommitCache(fake_pbft, prepare_req, highest, invalid_height, invalid_hash,
        fake_pbft.consensus()->minValidNodes(), 0);

    /// case1: invalid view, return directly(import failed)
    fake_pbft.consensus()->setView(prepare_req->view + 1);
    fake_pbft.consensus()->checkAndCommit();
    /// check submit failed
    CheckBlockChain(fake_pbft, block_number);
    /// check update commitedPrepareCache failed

    BOOST_CHECK(fake_pbft.consensus()->reqCache()->rawPrepareCache() !=
                fake_pbft.consensus()->reqCache()->committedPrepareCache());

    /// check backupMsg failed
    bytes data = bytes();
    checkBackupMsg(fake_pbft, FakePBFTEngine::backupKeyCommitted(), data);

    //// check no broadcasft
    BOOST_CHECK(fake_pbft.consensus()->broadcastFilter(
                    peer_keyPair.pub(), CommitReqPacket, prepare_req->uniqueKey()) == false);

    /// case2: valid view
    fake_pbft.consensus()->setView(prepare_req->view);
    fake_pbft.consensus()->checkAndCommit();
    /// check backupMsg succ
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->rawPrepareCache() ==
                fake_pbft.consensus()->reqCache()->committedPrepareCache());
    fake_pbft.consensus()->reqCache()->committedPrepareCache().encode(data);
    checkBackupMsg(fake_pbft, FakePBFTEngine::backupKeyCommitted(), data);
    /// submit failed for collected commitReq is not enough
    CheckBlockChain(fake_pbft, block_number);

    /// case3: enough commitReq && SignReq
    fake_pbft.consensus()->reqCache()->clearAll();
    FakeSignAndCommitCache(fake_pbft, prepare_req, highest, invalid_height, invalid_hash,
        fake_pbft.consensus()->minValidNodes(), 2);
    fake_pbft.consensus()->checkAndCommit();
    /// check backupMsg succ
    fake_pbft.consensus()->reqCache()->committedPrepareCache().encode(data);
    checkBackupMsg(fake_pbft, FakePBFTEngine::backupKeyCommitted(), data);
    /// check submit block scc
    CheckBlockChain(fake_pbft, block_number + 1);
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->rawPrepareCache() ==
                fake_pbft.consensus()->reqCache()->committedPrepareCache());
    /// check reportBlock
    /// checkReportBlock(fake_pbft, highest, false);
    /// BOOST_CHECK(fake_pbft.consensus()->timeManager().m_lastSignTime <= utcTime());
}
/// test isValidPrepare
BOOST_AUTO_TEST_CASE(testIsValidPrepare)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    PrepareReq::Ptr req = std::make_shared<PrepareReq>();
    TestIsValidPrepare(fake_pbft, req, true);
    TestIsValidPrepare(fake_pbft, req, false);
}
/// test handlePrepareReq
BOOST_AUTO_TEST_CASE(testHandlePrepareReq)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    fake_pbft.consensus()->initPBFTEnv(
        3 * (fake_pbft.consensus()->timeManager().m_emptyBlockGenTime));
    PrepareReq::Ptr req = std::make_shared<PrepareReq>();
    TestIsValidPrepare(fake_pbft, req, true);
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        appendSessionInfo(fake_pbft, fake_pbft.m_sealerList[i]);
    }
    /// case1: without omit empty block, re-generate prepareReq and broadcast
    fake_pbft.consensus()->reqCache()->clearAll();
    fake_pbft.consensus()->setOmitEmpty(false);
    fake_pbft.consensus()->handlePrepareMsg(req);
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->prepareCache().block_hash == req->block_hash);
    /// check broadcastSign
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        compareAsyncSendTime(fake_pbft, fake_pbft.m_sealerList[i], 1);
    }
    /// check checkAndCommit(without enough sign and commit requests)
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->rawPrepareCache() !=
                fake_pbft.consensus()->reqCache()->committedPrepareCache());

    /// case2: with enough sign and commit: submit block
    PrepareReq::Ptr copiedReq = std::make_shared<PrepareReq>(*req);
    fake_pbft.consensus()->reqCache()->clearAll();
    BlockHeader highest;
    FakeSignAndCommitCache(fake_pbft, copiedReq, highest, 0, 0,
        fake_pbft.consensus()->minValidNodes() - 1, 2, false, false);
    int64_t block_number = obtainBlockNumber(fake_pbft);
    fake_pbft.consensus()->handlePrepareMsg(copiedReq);

    /// BOOST_CHECK(fake_pbft.consensus()->reqCache()->prepareCache().block_hash == h256());
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->rawPrepareCache() ==
                fake_pbft.consensus()->reqCache()->committedPrepareCache());
    bytes data;
    fake_pbft.consensus()->reqCache()->committedPrepareCache().encode(data);
    // since commit backup pbft asyncly, need to sleep 1s before checkBackupMsg
    this_thread::sleep_for(std::chrono::milliseconds(1));
    checkBackupMsg(fake_pbft, FakePBFTEngine::backupKeyCommitted(), data);
    /// submit failed for collected commitReq is not enough
    CheckBlockChain(fake_pbft, block_number + 1);
}

BOOST_AUTO_TEST_CASE(testIsValidSignReq)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    PBFTMsgPacket pbftMsg;
    SignReq::Ptr signReq = std::make_shared<SignReq>();
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    KeyPair peer_keyPair = KeyPair::create();
    TestIsValidSignReq(fake_pbft, pbftMsg, signReq, prepareReq, peer_keyPair, false);
}

/// test handleSignMsg
BOOST_AUTO_TEST_CASE(testHandleSignMsg)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);

    fake_pbft.consensus()->initPBFTEnv(
        fake_pbft.consensus()->timeManager().m_emptyBlockGenTime * 3);

    PBFTMsgPacket pbftMsg;
    SignReq::Ptr signReq = std::make_shared<SignReq>();
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    KeyPair peer_keyPair = KeyPair::create();
    TestIsValidSignReq(fake_pbft, pbftMsg, signReq, prepareReq, peer_keyPair, true);
    /// test handleSignMsg
    /// case1: without enough signReq and commitReq, only addSign to the cache
    SignReq::Ptr signReq2 = std::make_shared<SignReq>();
    int64_t block_number = obtainBlockNumber(fake_pbft);
    fake_pbft.consensus()->handleSignMsg(signReq2, pbftMsg);
    BOOST_CHECK(*signReq2 == *signReq);
    /// check the signReq has been added to the cache
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->isExistSign(*signReq2));
    CheckBlockChain(fake_pbft, block_number);

    /// case2： with enough SignReq
    fake_pbft.consensus()->reqCache()->clearAll();
    PrepareReq::Ptr copiedPrepare = std::make_shared<PrepareReq>(*prepareReq);
    fake_pbft.consensus()->reqCache()->addPrepareReq(copiedPrepare);
    BlockHeader highest;
    FakeSignAndCommitCache(fake_pbft, prepareReq, highest, 0, 0,
        fake_pbft.consensus()->minValidNodes() - 1, 0, false, false);
    fake_pbft.consensus()->handleSignMsg(signReq2, pbftMsg);

    BOOST_CHECK(fake_pbft.consensus()->reqCache()->committedPrepareCache() ==
                fake_pbft.consensus()->reqCache()->rawPrepareCache());
    /// check backupMsg
    bytes data;
    fake_pbft.consensus()->reqCache()->committedPrepareCache().encode(data);
    checkBackupMsg(fake_pbft, FakePBFTEngine::backupKeyCommitted(), data);
    CheckBlockChain(fake_pbft, block_number);

    /// case3: with enough SignReq and CommitReq
    fake_pbft.consensus()->reqCache()->clearAll();
    copiedPrepare = std::make_shared<PrepareReq>(*prepareReq);
    fake_pbft.consensus()->reqCache()->addPrepareReq(copiedPrepare);
    FakeSignAndCommitCache(fake_pbft, prepareReq, highest, 0, 0,
        fake_pbft.consensus()->minValidNodes() - 1, 2, false, false);
    fake_pbft.consensus()->handleSignMsg(signReq2, pbftMsg);

    BOOST_CHECK(fake_pbft.consensus()->reqCache()->committedPrepareCache() ==
                fake_pbft.consensus()->reqCache()->rawPrepareCache());
    /// check backupMsg
    fake_pbft.consensus()->reqCache()->committedPrepareCache().encode(data);
    checkBackupMsg(fake_pbft, FakePBFTEngine::backupKeyCommitted(), data);
    CheckBlockChain(fake_pbft, block_number + 1);
}

BOOST_AUTO_TEST_CASE(testIsCommitReqValid)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    PBFTMsgPacket pbftMsg;
    CommitReq::Ptr commitReq = std::make_shared<CommitReq>();
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    KeyPair peer_keyPair = KeyPair::create();
    TestIsValidCommitReq(fake_pbft, pbftMsg, commitReq, prepareReq, peer_keyPair, false);
}

/// test handleCommitMsg
BOOST_AUTO_TEST_CASE(testHandleCommitMsg)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    PBFTMsgPacket pbftMsg;
    CommitReq::Ptr commitReq = std::make_shared<CommitReq>();
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    KeyPair peer_keyPair = KeyPair::create();
    TestIsValidCommitReq(fake_pbft, pbftMsg, commitReq, prepareReq, peer_keyPair, true);
    /// case1: without enough signReq and commitReq
    CommitReq::Ptr commitReq2 = std::make_shared<CommitReq>();
    int64_t block_number = obtainBlockNumber(fake_pbft);
    fake_pbft.consensus()->handleCommitMsg(commitReq2, pbftMsg);
    BOOST_CHECK(*commitReq2 == *commitReq);
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->isExistCommit(*commitReq2));
    CheckBlockChain(fake_pbft, block_number);

    /// case2: with enough signReq but not commitReq
    fake_pbft.consensus()->reqCache()->clearAll();
    // in case of prepareReq is cleared.
    std::shared_ptr<PrepareReq> copiedPrepare = std::make_shared<PrepareReq>(*prepareReq);
    fake_pbft.consensus()->reqCache()->addPrepareReq(copiedPrepare);
    BlockHeader highest;
    FakeSignAndCommitCache(fake_pbft, prepareReq, highest, 0, 0,
        fake_pbft.consensus()->minValidNodes(), 0, false, false);
    fake_pbft.consensus()->handleCommitMsg(commitReq2, pbftMsg);
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->isExistCommit(*commitReq2));
    CheckBlockChain(fake_pbft, block_number);

    /// case3 : with enough signReq and commitReq
    fake_pbft.consensus()->reqCache()->clearAll();
    copiedPrepare = std::make_shared<PrepareReq>(*prepareReq);
    fake_pbft.consensus()->reqCache()->addPrepareReq(copiedPrepare);
    FakeSignAndCommitCache(fake_pbft, prepareReq, highest, 0, 0,
        fake_pbft.consensus()->minValidNodes(), 2, false, false);
    fake_pbft.consensus()->handleCommitMsg(commitReq2, pbftMsg);
    CheckBlockChain(fake_pbft, block_number + 1);
}

BOOST_AUTO_TEST_CASE(testShouldSeal)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    /// case 1: m_cfgErr is true
    fake_pbft.consensus()->resetConfig();
    BOOST_CHECK(fake_pbft.consensus()->shouldSeal() == false);

    /// case 2: the node is a sealer, but getLeader failed for highest is a invalid block
    FakePBFTSealer(fake_pbft);
    BOOST_CHECK(fake_pbft.consensus()->shouldSeal() == false);

    /// case 3: normal case
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    TestIsValidPrepare(fake_pbft, prepareReq, true);

    fake_pbft.consensus()->setNodeIdx(fake_pbft.consensus()->getLeader().second);
    BOOST_CHECK(fake_pbft.consensus()->shouldSeal() == true);
    /// update the committedPrepareCache, callback reHandleCommitPrepareCache
    fake_pbft.consensus()->setConsensusBlockNumber(prepareReq->height);

    fake_pbft.consensus()->reqCache()->addRawPrepare(prepareReq);
    fake_pbft.consensus()->reqCache()->updateCommittedPrepare();
    /// update the rawPrepareReq
    PrepareReq::Ptr new_req = std::make_shared<PrepareReq>();
    new_req->height = fake_pbft.consensus()->consensusBlockNumber() + 1;
    fake_pbft.consensus()->reqCache()->addRawPrepare(new_req);

    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        appendSessionInfo(fake_pbft, fake_pbft.m_sealerList[i]);
    }
    BOOST_CHECK(fake_pbft.consensus()->shouldSeal() == false);
    testReHandleCommitPrepareCache(fake_pbft, *prepareReq);

    ///====== test notifySealing ======
    FakeBlock block(12, KeyPair::create().secret(), fake_pbft.consensus()->consensusBlockNumber());
    fake_pbft.consensus()->onNotifyNextLeaderReset();
    /// the leader calls notifySealing
    fake_pbft.consensus()->setNodeIdx(fake_pbft.consensus()->getLeader().second);
    BOOST_CHECK(fake_pbft.consensus()->notifyNextLeaderSeal() == false);
    fake_pbft.consensus()->notifySealing(*block.m_block);
    BOOST_CHECK(fake_pbft.consensus()->notifyNextLeaderSeal() == false);

    /// the nextLeader calls notifySealing
    fake_pbft.consensus()->setNodeIdx(fake_pbft.consensus()->getNextLeader());
    fake_pbft.consensus()->notifySealing(*block.m_block);
    BOOST_CHECK(fake_pbft.consensus()->notifyNextLeaderSeal() == true);
    ///====== test notifySealing end ======

    /// case 4: the node is not the leader
    fake_pbft.consensus()->setNodeIdx(
        (fake_pbft.consensus()->getLeader().second + 1) % fake_pbft.consensus()->nodeNum());
    FakeService* fake_service =
        dynamic_cast<FakeService*>(fake_pbft.consensus()->mutableService().get());
    fake_service->setConnected();
    BOOST_CHECK(fake_pbft.consensus()->shouldSeal() == false);
}

BOOST_AUTO_TEST_CASE(testCollectGarbage)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    BlockHeader highest;
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    FakeSignAndCommitCache(
        fake_pbft, prepareReq, highest, 0, 0, fake_pbft.consensus()->minValidNodes(), 2);
    fake_pbft.consensus()->mutableTimeManager().m_lastGarbageCollection =
        std::chrono::steady_clock::now();
    /// can't trigger collectGarbage
    fake_pbft.consensus()->collectGarbage();
    auto minValidNodesSize = fake_pbft.consensus()->minValidNodes();
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->getSigCacheSize(
                    prepareReq->block_hash, minValidNodesSize) == minValidNodesSize);
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->getCommitCacheSize(
                    prepareReq->block_hash, minValidNodesSize) == minValidNodesSize);

    /// can trigger collectGarbage
    fake_pbft.consensus()->mutableTimeManager().m_lastGarbageCollection =
        std::chrono::steady_clock::now() -
        std::chrono::seconds(fake_pbft.consensus()->timeManager().CollectInterval + 10);
    fake_pbft.consensus()->collectGarbage();
    BOOST_CHECK(
        fake_pbft.consensus()->reqCache()->getSigCacheSize(highest.hash(), minValidNodesSize) == 0);
    BOOST_CHECK(fake_pbft.consensus()->reqCache()->getCommitCacheSize(
                    highest.hash(), minValidNodesSize) == 0);
}
/// test handle future block
BOOST_AUTO_TEST_CASE(testHandleFutureBlock)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    TestIsValidPrepare(fake_pbft, prepareReq, true);
    prepareReq->height = fake_pbft.consensus()->consensusBlockNumber();
    prepareReq->view = fake_pbft.consensus()->view();
    fake_pbft.consensus()->reqCache()->addFuturePrepareCache(prepareReq);
    BOOST_CHECK(
        fake_pbft.consensus()->reqCache()->futurePrepareCache(prepareReq->height)->block_hash !=
        h256());
    fake_pbft.consensus()->handleFutureBlock();
    /// check the functurePrepareCache has been cleared
    BOOST_CHECK(
        fake_pbft.consensus()->reqCache()->futurePrepareCache(prepareReq->height) == nullptr);
}

/// test handleViewChangeMsg
BOOST_AUTO_TEST_CASE(testHandleViewchangeMsg)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(4, ProtocolID::PBFT);
    fake_pbft.consensus()->resetConfig();
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        appendSessionInfo(fake_pbft, fake_pbft.m_sealerList[i]);
    }
    ViewChangeReq::Ptr viewchange_req = std::make_shared<ViewChangeReq>();
    PBFTMsgPacket viewchange_packet;
    /// construct viewchange triggered by the 0th sealer
    TestIsValidViewChange(fake_pbft, viewchange_req);
    IDXTYPE nodeIdxSource = 2;
    FakePBFTMsgPacket(viewchange_packet, *viewchange_req, ViewChangeReqPacket, nodeIdxSource,
        fake_pbft.m_sealerList[nodeIdxSource]);

    /// test handleViewChangeReq
    ViewChangeReq::Ptr viewchange_req_decoded = std::make_shared<ViewChangeReq>();
    fake_pbft.consensus()->handleViewChangeMsg(viewchange_req_decoded, viewchange_packet);
    BOOST_CHECK(*viewchange_req_decoded == *viewchange_req);
    /// viewchange no consensused
    BOOST_CHECK(fake_pbft.consensus()->view() != viewchange_req_decoded->view);

    /// fake viewchange req generated by the 1st sealer
    fakeValidViewchange(fake_pbft, *viewchange_req, 1);
    FakePBFTMsgPacket(viewchange_packet, *viewchange_req, ViewChangeReqPacket, nodeIdxSource,
        fake_pbft.m_sealerList[nodeIdxSource]);
    fake_pbft.consensus()->handleViewChangeMsg(viewchange_req_decoded, viewchange_packet);
    BOOST_CHECK(*viewchange_req_decoded == *viewchange_req);
    /// viewchange no consensused
    BOOST_CHECK(fake_pbft.consensus()->view() == viewchange_req_decoded->view);
}

BOOST_AUTO_TEST_CASE(testFastViewChange)
{
    /// test fast view change
    FakeConsensus<FakePBFTEngine> fake_pbft(4, ProtocolID::PBFT);
    fake_pbft.consensus()->resetConfig();
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        appendSessionInfo(fake_pbft, fake_pbft.m_sealerList[i]);
    }
    ViewChangeReq::Ptr viewchange_req = std::make_shared<ViewChangeReq>();
    PBFTMsgPacket viewchange_packet;
    IDXTYPE nodeIdxSource = 1;
    /// handle the viewchange request received from 2nd sealer
    fakeValidViewchange(fake_pbft, *viewchange_req, 0, true);
    FakePBFTMsgPacket(viewchange_packet, *viewchange_req, ViewChangeReqPacket, nodeIdxSource,
        fake_pbft.m_sealerList[nodeIdxSource]);
    fake_pbft.consensus()->handleViewChangeMsg(viewchange_req, viewchange_packet);
    BOOST_CHECK(fake_pbft.consensus()->toView() != viewchange_req->view - 1);

    nodeIdxSource = 2;
    /// handle the viewchange request received from 1st sealer
    fakeValidViewchange(fake_pbft, *viewchange_req, 1, true);
    FakePBFTMsgPacket(viewchange_packet, *viewchange_req, ViewChangeReqPacket, nodeIdxSource,
        fake_pbft.m_sealerList[nodeIdxSource]);
    fake_pbft.consensus()->handleViewChangeMsg(viewchange_req, viewchange_packet);
    BOOST_CHECK(fake_pbft.consensus()->toView() == viewchange_req->view - 1);

    /// the node trigger fast viewchange, broadcast ViewChangeReq and changeView
    fake_pbft.consensus()->checkTimeout();
    BOOST_CHECK(fake_pbft.consensus()->view() == viewchange_req->view);
}

/// test checkBlock
BOOST_AUTO_TEST_CASE(testCheckBlock)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(13, ProtocolID::PBFT);
    fake_pbft.consensus()->resetConfig();
    /// ignore the genesis block
    BOOST_CHECK(fake_pbft.consensus()->checkBlock(
                    *fake_pbft.consensus()->blockChain()->getBlockByNumber(0)) == true);

    ///  check sealerList
    BOOST_CHECK(fake_pbft.consensus()->checkBlock(
                    *fake_pbft.consensus()->blockChain()->getBlockByNumber(1)) == false);

    /// fake sealerList: check sealerList && sealer passed && sign
    FakeBlock block(12, KeyPair::create().secret(), 1);
    BOOST_CHECK(fake_pbft.consensus()->checkBlock(*block.m_block) == false);
    fake_pbft.consensus()->setSealerList(block.m_block->blockHeader().sealerList());
    BOOST_CHECK(fake_pbft.consensus()->checkBlock(*block.m_block) == true);

    /// block with too-many transactions
    fake_pbft.consensus()->setMaxBlockTransactions(11);
    BOOST_CHECK(fake_pbft.consensus()->checkBlock(*block.m_block) == false);

    /// block with not-enough sealer
    FakeBlock invalid_block(7, KeyPair::create().secret(), 1);
    BOOST_CHECK(fake_pbft.consensus()->checkBlock(*invalid_block.m_block) == false);
}

/// test handleMsg
BOOST_AUTO_TEST_CASE(testHandleMsg)
{
    /// test fast view change
    FakeConsensus<FakePBFTEngine> fake_pbft(4, ProtocolID::PBFT);
    fake_pbft.consensus()->resetConfig();
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        appendSessionInfo(fake_pbft, fake_pbft.m_sealerList[i]);
    }
    /// handleViewChangeMsg
    ViewChangeReq viewchange_req;
    PBFTMsgPacket viewchange_packet;
    IDXTYPE nodeIdxSource = 1;
    /// handle the viewchange request received from 2nd sealer
    fakeValidViewchange(fake_pbft, viewchange_req, 0, true);
    FakePBFTMsgPacket(viewchange_packet, viewchange_req, ViewChangeReqPacket, nodeIdxSource,
        fake_pbft.m_sealerList[nodeIdxSource]);

    viewchange_packet.ttl = 1;
    /// no broadcast for ttl
    fake_pbft.consensus()->handleMsg(viewchange_packet);
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        compareAsyncSendTime(fake_pbft, fake_pbft.m_sealerList[i], 0);
    }

    viewchange_packet.ttl = 2;
    /// no broadcast for invalid viewchange request
    fake_pbft.consensus()->handleMsg(viewchange_packet);
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        compareAsyncSendTime(fake_pbft, fake_pbft.m_sealerList[i], 0);
    }

    /// broadcast
    fakeValidViewchange(fake_pbft, viewchange_req, 2, true);
    FakePBFTMsgPacket(viewchange_packet, viewchange_req, ViewChangeReqPacket, nodeIdxSource,
        fake_pbft.m_sealerList[nodeIdxSource]);
    fake_pbft.consensus()->handleMsg(viewchange_packet);
    for (size_t i = 0; i < fake_pbft.m_sealerList.size(); i++)
    {
        if (fake_pbft.m_sealerList[i] != viewchange_packet.node_id && i != viewchange_req.idx)
        {
            compareAsyncSendTime(fake_pbft, fake_pbft.m_sealerList[i], 1);
        }
        /// don't broadcast to the source node
        else
        {
            compareAsyncSendTime(fake_pbft, fake_pbft.m_sealerList[i], 0);
        }
    }
}

BOOST_AUTO_TEST_CASE(testCreatePBFTReqCache)
{
    FakeConsensus<FakePBFTEngine> fake_pbft(1, ProtocolID::PBFT);
    // create PartiallyPrepareReq
    fake_pbft.consensus()->setEnablePrepareWithTxsHash(true);
    fake_pbft.consensus()->createPBFTReqCache();
    BOOST_CHECK(std::dynamic_pointer_cast<PartiallyPBFTReqCache>(
                    fake_pbft.consensus()->reqCache()) != nullptr);

    // create reqCache
    fake_pbft.consensus()->setEnablePrepareWithTxsHash(false);
    fake_pbft.consensus()->createPBFTReqCache();
    BOOST_CHECK(std::dynamic_pointer_cast<PartiallyPBFTReqCache>(
                    fake_pbft.consensus()->reqCache()) == nullptr);
}
BOOST_AUTO_TEST_CASE(testHandlePartiallyPrepare)
{
    std::shared_ptr<FakeConsensus<FakePBFTEngine>> leaderPBFT =
        std::make_shared<FakeConsensus<FakePBFTEngine>>(4, ProtocolID::PBFT);
    std::shared_ptr<FakeConsensus<FakePBFTEngine>> followerPBFT =
        std::make_shared<FakeConsensus<FakePBFTEngine>>(4, ProtocolID::PBFT);

    std::shared_ptr<BlockFactory> blockFactory = std::make_shared<PartiallyBlockFactory>();
    // the leader construct prepareReq
    PrepareReq::Ptr prepareReq = std::make_shared<PrepareReq>();
    fakePrepareMsg(leaderPBFT, followerPBFT, prepareReq, blockFactory, true);

    auto sendedPrepare = leaderPBFT->consensus()->wrapperConstructPrepareReq(prepareReq->pBlock);
    BOOST_CHECK(sendedPrepare->idx == prepareReq->idx);
    BOOST_CHECK(sendedPrepare->view == prepareReq->view);

    // get the broadcasted p2p message
    std::shared_ptr<FakeService> leaderService =
        std::dynamic_pointer_cast<FakeService>(leaderPBFT->consensus()->mutableService());
    std::shared_ptr<P2PMessage> receivedP2pMsg = nullptr;
    // wait broadcast enqueue finished
    while (!receivedP2pMsg)
    {
        receivedP2pMsg =
            leaderService->getAsyncSendMessageByNodeID(followerPBFT->consensus()->keyPair().pub());
        this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    for (auto const& nodeId : leaderPBFT->m_sealerList)
    {
        compareAndClearAsyncSendTime(*leaderPBFT, nodeId, 1);
    }
    BOOST_CHECK(receivedP2pMsg != nullptr);

    // test handleP2PMessage(handlePartiallyPrepare)
    std::shared_ptr<FakeSession> session =
        std::make_shared<FakeSession>(leaderPBFT->consensus()->keyPair().pub());

    dev::p2p::NetworkException _exception;
    followerPBFT->consensus()->wrapperHandleP2PMessage(_exception, session, receivedP2pMsg);

    // the follower handlePartiallyPrepare and request missed transactions to the leader
    std::shared_ptr<FakeService> followService =
        std::dynamic_pointer_cast<FakeService>(followerPBFT->consensus()->mutableService());
    receivedP2pMsg = nullptr;
    while (!receivedP2pMsg)
    {
        receivedP2pMsg =
            followService->getAsyncSendMessageByNodeID(leaderPBFT->consensus()->keyPair().pub());
        this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    // the follower handlePartiallyPrepare and request missed transactions to the leader
    compareAndClearAsyncSendTime(*followerPBFT, leaderPBFT->consensus()->keyPair().pub(), 1);

    BOOST_CHECK(receivedP2pMsg != nullptr);

    checkPrepareReqEqual(
        followerPBFT->consensus()->partiallyReqCache()->partiallyRawPrepare(), sendedPrepare);

    // the leader response the requested-transactions
    session->m_id = followerPBFT->consensus()->keyPair().pub();
    leaderPBFT->consensus()->wrapperHandleP2PMessage(_exception, session, receivedP2pMsg);

    receivedP2pMsg = nullptr;
    while (!receivedP2pMsg)
    {
        receivedP2pMsg =
            leaderService->getAsyncSendMessageByNodeID(followerPBFT->consensus()->keyPair().pub());
        this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    compareAndClearAsyncSendTime(*leaderPBFT, followerPBFT->consensus()->keyPair().pub(), 1);
    BOOST_CHECK(receivedP2pMsg != nullptr);

    // the follower get missed transaction from the leader and fill the block and addRawPrepare
    session->m_id = leaderPBFT->consensus()->keyPair().pub();
    followerPBFT->consensus()->wrapperHandleP2PMessage(_exception, session, receivedP2pMsg);
    receivedP2pMsg = nullptr;
    while (!receivedP2pMsg)
    {
        receivedP2pMsg =
            followService->getAsyncSendMessageByNodeID(leaderPBFT->consensus()->keyPair().pub());
        this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    compareAndClearAsyncSendTime(*followerPBFT, leaderPBFT->consensus()->keyPair().pub(), 1);
    checkPrepareReqEqual(
        followerPBFT->consensus()->partiallyReqCache()->rawPrepareCachePtr(), sendedPrepare);

    // check transactions of the blocks
    size_t i = 0;
    for (auto tx : *(followerPBFT->consensus()
                         ->partiallyReqCache()
                         ->rawPrepareCachePtr()
                         ->pBlock->transactions()))
    {
        BOOST_CHECK(*tx == *(*sendedPrepare->pBlock->transactions())[i]);
        i++;
    }
}

BOOST_AUTO_TEST_CASE(testResetConsensusTimeout)
{
    std::shared_ptr<FakeConsensus<FakePBFTEngine>> pbftFixture =
        std::make_shared<FakeConsensus<FakePBFTEngine>>(4, ProtocolID::PBFT);
    // first setup, initPBFTEnv
    pbftFixture->consensus()->initPBFTEnv(3000);
    auto& timeMgr = pbftFixture->consensus()->mutableTimeManager();
    // reset consensus_timeout to 30min
    timeMgr.resetConsensusTimeout(1000 * 60 * 30);
    // case1: first setup, and viewchange for network reason

    auto now = timeMgr.m_lastConsensusTime + 3001;
    BOOST_CHECK(timeMgr.isTimeout(now) == true);
    // reach view consensus
    timeMgr.m_lastConsensusTime = timeMgr.m_lastSignTime = now;

    // case2: receive rawPrepare, and execution spent too much time(15min)
    timeMgr.m_lastAddRawPrepareTime = now + 100;
    now += 1000 * 60 * 15;
    BOOST_CHECK(timeMgr.isTimeout(now) == false);
    auto invalidNow = now + 1000 * 60 * 15 + 100;
    // execution timeout
    BOOST_CHECK(timeMgr.isTimeout(invalidNow) == true);

    // case3: receive rawPrepare, and execution without timeout
    timeMgr.m_lastExecTime = now;
    BOOST_CHECK(timeMgr.isTimeout(timeMgr.m_lastExecTime + 2000) == false);
    // network timeout
    invalidNow = timeMgr.m_lastExecTime + 3001;
    BOOST_CHECK(timeMgr.isTimeout(invalidNow) == true);
    // reach view consensus
    timeMgr.m_lastConsensusTime = invalidNow;

    // receive no rawPrepare after 3s
    now = invalidNow + 3;
    BOOST_CHECK(timeMgr.isTimeout() == true);

    // reach view consensus
    timeMgr.m_lastConsensusTime = invalidNow;

    // receive prepare after 2s
    now += 2000;
    timeMgr.m_lastAddRawPrepareTime = now;
    // execution spent 20min
    now += 1000 * 60 * 20;
    BOOST_CHECK(timeMgr.isTimeout(now) == false);
    invalidNow = now + 10 * 1000 * 60 * +100;
    BOOST_CHECK(timeMgr.isTimeout(invalidNow) == true);
    timeMgr.m_lastExecTime = now;
    BOOST_CHECK(timeMgr.isTimeout(now) == false);
    timeMgr.m_lastConsensusTime = now + 1000;

    // case4: fast view change, the leader
    timeMgr.changeView();
    now += 3002;
    BOOST_CHECK(timeMgr.isTimeout(now) == true);

    // case5: fast view change, the  follow
    // without receive the empty block after 3s
    timeMgr.m_lastExecTime = now - 2000;
    timeMgr.m_lastConsensusTime = now;
    BOOST_CHECK(timeMgr.isTimeout(now + 3001) == true);

    // receive the empty block after 100ms
    now += 100;
    timeMgr.m_lastAddRawPrepareTime = now;
    // suppose block decode time is 20ms
    now += 20;
    BOOST_CHECK(timeMgr.isTimeout(now) == false);
    timeMgr.changeView();
    BOOST_CHECK(timeMgr.isTimeout(now) == true);
}
BOOST_AUTO_TEST_SUITE_END()
}  // namespace test
}  // namespace dev
